﻿/* ----------------------------------------------------------------------------
 * Copyright (c) Philip Stears 
 *
 * This source code is subject to terms and conditions of the Microsoft Public 
 * License (Ms-PL).
 * 
 * By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 * 
 * To request a copy of the Microsoft Public License, send a request by email
 * to license@connectbasic.net.
 *
 * This notice must not be removed from this software.
 * ---------------------------------------------------------------------------- */

namespace ConnectBasic.Shell.Implementation
{
    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Xml.Linq;
    using ConnectBasic.Shell.Extensibility;
    using ConnectBasic.Shell.Hosting;

    /// <summary>
    /// Provides the default implementation of the 
    /// <see cref="IShell" /> interface for hosting the ConnectBasic
    /// shell.
    /// </summary>
    internal class DefaultShell : IShell
    {
        private IShellHost mHost;
        private IProjectCollection mProjects;
        private IToolWindowCollection mToolWindows;
        private IToolWindowZoneCollection mZones;
        private DefaultShellStateCollection mStates;
        private IDebuggerService mDebuggerService = new DefaultShellDebuggerService();
        private MacroIdeForm mForm;

        #region .ctor

        internal DefaultShell(IShellHost host)
        {
            if (host == null) throw new ArgumentNullException("host");
            mHost = host;

            // Wrap the host's project collection 
            mProjects = new DefaultShellProjectCollection(host.Projects);

            // Window handling - order is important

            //  1. Register states, windows, zones
            this.RegisterStates();
            this.RegisterToolWindows();
            this.RegisterZones();

            //  2. Load tool window layout, needs:
            //     States, ToolWindows, and Zones
            this.LoadLayout();            
        }

        #endregion

        #region Initialization - States

        private void RegisterStates()
        {
            var states = new DefaultShellStateCollection();

            states.Add(WellKnownStateNames.Default);
            states.Add(WellKnownStateNames.Running);
            
            states.ActivateState(WellKnownStateNames.Default);

            mStates = states;
        }

        #endregion

        #region Initialization - Zones

        private void RegisterZones()
        {
            var zones = new DefaultToolWindowZoneCollection();


            // TODO: Dynamic zones system
            zones.Add("Floating");
            zones.Add("LeftTop");
            zones.Add("LeftBottom");
            zones.Add("RightBottom");

            mZones = zones;
        }

        #endregion

        #region Initialization - Tool Windows

        private void RegisterToolWindows()
        {
            var toolWindows = new DefaultShellToolWindowCollection();

            // TODO: The coupling here is so very badly wrong, should probably introduce some sort of IExtension/IPackage interface
            // instead
            toolWindows.Add(SolutionExplorerToolWindow.InvariantName, Properties.Resources.DisplayNameSolutionExplorer, typeof(SolutionExplorerToolWindow));
            toolWindows.Add(PropertyGridToolWindow.InvariantName, Properties.Resources.DisplayNamePropertyGrid, typeof(PropertyGridToolWindow));
            toolWindows.Add(LocalsToolWindow.InvariantName, Properties.Resources.DisplayNameLocals, typeof(LocalsToolWindow));

            mToolWindows = toolWindows;
        }

        #endregion

        #region Initialization - Layout

        private void LoadLayout()
        {
            // TODO: Custom layout loading
            this.LoadDefaultLayout();
        }

        private void LoadDefaultLayout()
        {
            XDocument layout;

            // Read in the layout file from our embedded 
            // resource
            using (var layoutStream = typeof(DefaultShell).Assembly.GetManifestResourceStream(typeof(DefaultShell), "DefaultToolWindowLayout.xml"))
            {
                Debug.Assert(layoutStream != null);

                using (var layoutReader = new StreamReader(layoutStream))
                {
                    layout = XDocument.Load(layoutReader);
                }
            }

            // Load it
            LoadLayoutFromXDocument(layout);
        }

        private void LoadLayoutFromXDocument(XDocument layoutDocument)
        {
            // TODO: XML Schema Validation

            // Cache the root and namespace 
            var root = layoutDocument.Root;
            var ns = root.Name.Namespace;
            
            // Process each state element for which there is 
            // a corresponding state
            foreach (var stateElement in root.Elements(ns + "State"))
            {
                var name = stateElement.GetAttributeValue("InvariantName");
                var state = mStates[name];

                if (state != null)
                {
                    LoadLayoutFromXDocument_State(ns, stateElement, state);
                }
            }
        }

        private void LoadLayoutFromXDocument_State(XNamespace ns, XElement stateElement, IShellState state)
        {

            // Process each zone element for which there is a 
            // coresponding zone
            foreach (var zoneElement in stateElement.Elements(ns + "Zone"))
            {
                var name = zoneElement.GetAttributeValue("InvariantName");
                var zone = mZones[name];

                if (zone != null)
                {
                    LoadLayoutFromXDocument_Zone(ns, state, zoneElement, zone);
                }
            }
        }

        private void LoadLayoutFromXDocument_Zone(XNamespace ns, IShellState state, XElement zoneElement, IToolWindowZone zone)
        {

            // Process each tool window element for which there is a 
            // coresponding tool window
            foreach (var windowElement in zoneElement.Elements(ns + "ToolWindow"))
            {
                var name = windowElement.GetAttributeValue("InvariantName");
                var window = mToolWindows[name];

                if (window != null)
                {
                    mZones.AttachWindow(zone, state, window);
                }
            }
        }

        #endregion

        #region IShell Members

        public bool IsVisible
        {
            get { return this.HasForm() && mForm.Visible; }
        }

        public void Show()
        {
            this.EnsureForm();
            mForm.Show();
        }

        public void Hide()
        {
            if (this.HasForm())
            {
                mForm.Hide();
            }
        }

        public IProjectCollection Projects
        {
            get { return mProjects; }
        }

        public IToolWindowCollection ToolWindows
        {
            get { return mToolWindows; }
        }

        public IToolWindowZoneCollection ToolWindowZones
        {
            get { return mZones; }
        }

        public IShellStateCollection States
        {
            get { return mStates; }
        }

        #endregion

        #region IServiceProvider Members

        public object GetService(Type serviceType)
        {
            if (serviceType == typeof(IEditorService) && this.HasForm())
            {
                return mForm.TheEditorZone;
            }

            if (serviceType == typeof(IDebuggerService))
            {
                return mDebuggerService;
            }

            return null;
        }

        #endregion

        #region Macro Execution Support

        internal void SetActiveMacro(IExecutableMacro macro)
        {
            macro.Started += new EventHandler(macro_Started);
            macro.Finished += new EventHandler(macro_Finished);
        }

        void macro_Started(object sender, EventArgs e)
        {
            
            // Change our state
            mStates.ActivateState("Running");
        }

        void macro_Finished(object sender, EventArgs e)
        {
            var macro = (IExecutableMacro)sender;

            // Make sure we are no longer bound to the macro's events
            macro.Started -= macro_Started;
            macro.Finished -= macro_Finished;

            // Reset our state
            mStates.ActivateState("Default");
        }

        #endregion

        #region Helpers

        private bool HasForm()
        {
            return mForm != null && !mForm.IsDisposed;
        }

        private void EnsureForm()
        {
            if (!HasForm())
            {
                mForm = new MacroIdeForm();
                mForm.Initialize(this);
            }
        }

        #endregion

    }
}
